package com.bluefox.modules.quartz.utils;

import com.bluefox.exception.BadRequestException;
import com.bluefox.modules.quartz.domain.QuartzJob;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.quartz.impl.triggers.CronTriggerImpl;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Date;

import static org.quartz.JobBuilder.newJob;
import static org.quartz.TriggerBuilder.newTrigger;

/**
 * @ClassName QuartzManage
 * @Description
 *     CronScheduleBuilder.cronSchedule
 *     SimpleScheduleBuilder.simpleSchedule
 *  *  Missfire
 *  *  所谓的Missfire就是错过触发，本应该在某个时间点触发的任务，由于一些原因没有触发。Missfire就是用来解决这个问题的。
 *  *  原因：
 *  *  在某个时刻触发的任务超过线程池中线程的数量，导致优先级低的Trigger没有触发；
 *  *
 *  *  eg:在某个时刻触发了10个任务，但是线程池中线程的数量只有3个，有7个Trigger没有触发；任务执行时长，超过触发的时间。
 *  *  eg:每隔3秒执行一次任务，但是任务执行需要7秒；CronTrigger和SimpleTrigger都定义了MissFire机制方法。
 *
 * @Author SunFeng
 * @Date 2020/2/28 15:23
 * @Version 1.0
 */
@Slf4j
@Component
public class QuartzManage {

    private static final String JOB_NAME = "JOB_";
    private static final String TRIGGER_NAME="TRIGGER_";
    //调度：Scheduler任务调度器，是实际执行任务调度的控制器。在spring中通过SchedulerFactoryBean封装起来。
    @Resource(name = "scheduler")
    private Scheduler scheduler;


    public void addJob(QuartzJob quartzJob) {
        try {
            // JobBuilder 构建一个jobDetail实例，将该实例与ExecutionJob绑定

            JobDetail jobDetail = newJob(ExecutionJob.class)
                    .withIdentity(JOB_NAME + quartzJob.getId())
                    //无触发器（Trigger）指向时是否需要持久化或删除JobDetails信息
                    .storeDurably()
                    .build();
            // TriggerBuilder用于定义/构建触发器实例 绑定标识后期进行使用
            //Trigger用于触发Job的执行。当你准备调度一个job时，你创建一个Trigger的实例，然后设置调度相关的属性。
            CronTrigger cronTrigger = newTrigger().withIdentity(TRIGGER_NAME + quartzJob.getId())
                    .startNow()//立即生效
                    //  .withPriority(6)// 优先级使用时机：线程池中的线程数量小于同时要执行的任务的数量；
                    .withSchedule(CronScheduleBuilder.cronSchedule(quartzJob.getCronExpression()))
                    .build();

            //JobDataMap 用来保存JobDetail运行时的信息，
            // JobDataMap的使用：.usingJobData("name","kyle")或者.getJobDataMap("name","kyle")
            cronTrigger.getJobDataMap().put(QuartzJob.JOB_KEY, quartzJob);
            //SimpleTrigger主要用于一次性执行的Job（只在某个特定的时间点执行一次），或者Job在特定的时间点执行，
            // 重复执行N次，每次执行间隔T个时间单位。
            //CronTrigger在基于日历的调度上非常有用，如“每个星期五的正午”，或者“每月的第十天的上午10:15”等。

            //重置启动时间
            ((CronTriggerImpl) cronTrigger).setStartTime(new Date());

            //调度器关联任务和触发器
            scheduler.scheduleJob(jobDetail, cronTrigger);

            // 判断新建的任务是否是暂停状态任务
            if (quartzJob.getIsPause()) {
                pauseJob(quartzJob);
            }
        } catch (Exception e) {
            log.error("创建定时任务失败", e);
            throw new BadRequestException("创建定时任务失败");
        }
    }

    /**
     * 更新job cron表达式
     *
     * @param quartzJob /
     */
    public void updateJobCron(QuartzJob quartzJob) {
        try {
            //获取triggerKey
            TriggerKey triggerKey = TriggerKey.triggerKey(TRIGGER_NAME + quartzJob.getId());
            //获取CronTrigger
            CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
            // 如果不存在则创建一个定时任务
            if (trigger == null) {
                addJob(quartzJob);
                trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
            }

            // 创建trigger
            trigger = trigger.getTriggerBuilder()
                    .withIdentity(triggerKey)
                    .withSchedule(CronScheduleBuilder.cronSchedule(quartzJob.getCronExpression()))
                    //  .withPriority(6)// 优先级使用时机：线程池中的线程数量小于同时要执行的任务的数量；
                    .build();
            //重置启动时间
            ((CronTriggerImpl) trigger).setStartTime(new Date());
            trigger.getJobDataMap().put(QuartzJob.JOB_KEY, quartzJob);

            //
            scheduler.rescheduleJob(triggerKey, trigger);
            // 暂停任务
            if (quartzJob.getIsPause()) {
                pauseJob(quartzJob);
            }
        } catch (Exception e) {
            log.error("更新定时任务失败,{}", e.toString());
            throw new BadRequestException("更新定时任务失败");
        }

    }

    /**
     * 删除一个job
     *
     * @param quartzJob /
     */
    public void deleteJob(QuartzJob quartzJob) {
        try {
            TriggerKey triggerKey = TriggerKey.triggerKey(TRIGGER_NAME + quartzJob.getId());
            //暂停触发器
            scheduler.pauseTrigger(triggerKey);
            //移除触发器
            scheduler.unscheduleJob(triggerKey);
            // 删除job
            scheduler.deleteJob(JobKey.jobKey(JOB_NAME + quartzJob.getId()));
        } catch (Exception e) {
            log.error("删除定时任务失败", e);
            throw new BadRequestException("删除定时任务失败");
        }
    }

    /**
     * 恢复一个job
     *
     * @param quartzJob /
     */
    public void resumeJob(QuartzJob quartzJob) {
        try {
            TriggerKey triggerKey = TriggerKey.triggerKey(TRIGGER_NAME + quartzJob.getId());
            CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
            // 如果触发器不存在需要重新创建一个任务
            if (trigger == null) {
                addJob(quartzJob);
            }
            JobKey jobKey = JobKey.jobKey(JOB_NAME + quartzJob.getId());
            scheduler.resumeJob(jobKey);
        } catch (Exception e) {
            log.error("恢复定时任务失败", e);
            throw new BadRequestException("恢复定时任务失败");
        }
    }

    /**
     * 立即执行job
     *
     * @param quartzJob /
     */
    public void runJobNow(QuartzJob quartzJob) {
        try {
            TriggerKey triggerKey = TriggerKey.triggerKey(TRIGGER_NAME + quartzJob.getId());
            CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
            // 如果不存在则创建一个定时任务
            if (trigger == null) {
                addJob(quartzJob);
            }
            JobDataMap dataMap = new JobDataMap();
            dataMap.put(QuartzJob.JOB_KEY, quartzJob);
            JobKey jobKey = JobKey.jobKey(JOB_NAME + quartzJob.getId());
            scheduler.triggerJob(jobKey, dataMap);
        } catch (Exception e) {
            log.error("定时任务执行失败", e);
            throw new BadRequestException("定时任务执行失败");
        }
    }

    /**
     * 暂停一个job
     *
     * @param quartzJob /
     */
    public void pauseJob(QuartzJob quartzJob) {
        try {
            TriggerKey triggerKey = TriggerKey.triggerKey(TRIGGER_NAME + quartzJob.getId());
            scheduler.pauseTrigger(triggerKey);
        } catch (Exception e) {
            log.error("定时任务暂停失败", e);
            throw new BadRequestException("定时任务暂停失败");
        }
    }
}
